// #![no_std]
#![feature(linked_list_remove)]
#![allow(unused)]

pub mod api;
mod call;
pub mod context;
pub mod defines;
pub mod errors;
pub mod handler;
pub mod infor;
pub mod model;
pub mod param;
pub mod protocols;
pub mod util;

extern crate alloc;

#[macro_use]
extern crate embed_std;

use alloc::boxed::Box;
use alloc::string::String;

use crate::api::{api, set_api, Request, RequestParam, Response, API_QUEUE_SIZE};
use crate::context::Context;
use crate::handler::Handler;
use crate::infor::SysInfo;
use crate::model::{CallArg, Prop};
use crate::protocols::Transport;
use crate::util::copy_slice;
use alloc::vec::Vec;
use core::panic::PanicInfo;
use embed_std::allocator::AllocatorMonitor;
use embed_std::errors::{Error, Result};
use embed_std::sync::arc::Arc;
use embed_std::thread::{sleep_ms, Builder};
use embed_std::time::{hw_now, now};
pub use param::PersisParams;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{sync_channel, Receiver};
use util::from_slice_with_null_to_str;
// #[panic_handler]
// fn panic(info: &PanicInfo) -> ! {
//     errorln!("panic: {:?}", info);
//     loop {}
// }

struct Callback {}
impl Callback {
    pub fn new() -> Self {
        Self {}
    }
}
impl Handler for Callback {
    fn on_set_props(&mut self, props: Vec<Prop>) -> Result<Vec<Prop>> {
        infoln!("set props, prop size {}", props.len());
        Ok(Vec::new())
    }

    fn on_call_method(&mut self, method_name: &str, args: Vec<CallArg>) -> Result<Vec<Prop>> {
        infoln!("call method {} arg len {}", method_name, args.len());
        Ok(Vec::new())
    }
}

pub struct SdkOptions {
    pub cb: Option<Box<dyn Handler>>,
    pub ip: String,
    pub port: u16,
    pub do_activiate: bool,
    pub mqtt_factory: Option<fn() -> Box<dyn mqtt::MqttClient>>,
    pub info_fetcher: Option<Box<dyn Transport>>,
}

impl SdkOptions {
    pub fn new() -> Self {
        Self {
            cb: None,
            ip: String::from("127.0.0.1"),
            port: 1883,
            do_activiate: false,
            mqtt_factory: None,
            info_fetcher: None,
        }
    }
}

pub fn iot_sdk_thread(mut optm: SdkOptions) {
    infoln!("load params");
    let param = param::PersisParams::get();
    let product_code = param.product_code_str();
    let device_uid = param.device_uid_str();
    let secret = param.secret_str();
    infoln!(
        "device_uid {} product_code {} secret {}",
        device_uid,
        product_code,
        secret
    );

    infoln!("init context");
    let cb = if optm.cb.is_some() {
        optm.cb.take().unwrap()
    } else {
        Box::new(Callback::new())
    };
    let factory = optm.mqtt_factory.take();
    let ctx = Context::new(cb, factory, optm.info_fetcher.take()).expect("init context failed");
    let (tx, rx) = sync_channel(API_QUEUE_SIZE as usize);
    set_api(tx);
    let mut last_ts = hw_now().as_millis() as u64;

    // let pull_ctx = ctx.clone();
    let is_quit = Arc::new(AtomicBool::new(false));
    // let pull_is_quit = is_quit.clone();
    // std::thread::spawn(move ||{
    //     infoln!("api handle thread>>>");
    //     handle_apis(ctx, rx, pull_is_quit);
    //     infoln!("api handle thread<<<");
    // });
    infoln!("session loop>>>");
    while !is_quit.load(Ordering::SeqCst) {
        if !ctx.is_connected() {
            let _res = ctx
                .connect(optm.ip.as_str(), optm.port)
                .and_then(|_| {
                    if optm.do_activiate {
                        match ctx.activate() {
                            Ok(secret) => {
                                infoln!("activate ok, secret {}", secret);
                                copy_slice(secret.as_bytes(), param.activate_secret.as_mut());
                                return Err(Error::Other(String::from(
                                    "try to use new secret to connect",
                                )));
                            }
                            Err(_) => {}
                        }
                    }
                    Ok(())
                })
                .map_err(|err| {
                    errorln!("connect/subscribe/activate/get_ota_version failed: {}", err);
                    ctx.disconnect();
                    sleep_ms(3000);
                    err
                });
        }

        handle_apis(&ctx, &rx, &is_quit, 500);

        let now = hw_now().as_millis() as u64;
        if now > last_ts + 60_000 {
            last_ts = now;
            let info = SysInfo::get();
            if let Err(e) = ctx.report_info(&info) {
                errorln!("report info error {}", e);
                sleep_ms(2000);
                continue;
            }
            infoln!("report info ok");
        }
    }
    infoln!("session loop<<<");
}

include!(concat!(env!("OUT_DIR"), "/build_info.rs"));

#[no_mangle]
pub extern "C" fn init_iot_sdk() {
    let mut opt = SdkOptions::new();

    #[cfg(feature = "paho_mqtt")]
    {
        opt.mqtt_factory = Some(mqtt::create_mqtt);
    }

    rust_init_iot_sdk(opt);
}

pub fn rust_init_iot_sdk(opt: SdkOptions) {
    let version = env!("CARGO_PKG_VERSION");
    infoln!("iot sdk version: {} built at {}", version, BUILD_TIME);

    let entry = move || {
        iot_sdk_thread(opt);
    };
    let res = Builder::new()
        // .stack_size(20_480)  //for release, NOTE: stack too small, segment fault!
        .stack_size(64 * 1024) //for debug
        .name(String::from("iot sdk thread"))
        .spawn(entry);
    if res.is_err() {
        errorln!("start ios sdk thread failed");
    }
}

pub struct Sdk {
    opts: SdkOptions,
    ctx: Context,
}

fn handle_apis(ctx: &Context, rx: &Receiver<Request>, is_quit: &Arc<AtomicBool>, to: u32) {
    const STEP: u32 = 20;
    let mut count = 0;
    loop {
        if let Ok(r) = rx.try_recv() {
            debugln!("got call request: {:?}", r.req);
            let result = handle_api(&ctx, &r, &is_quit);
            if let Some(tx) = r.resp {
                _ = tx.send(result).map_err(|e| {
                    errorln!("send api response failed: {}", e);
                });
            }
            continue;
        }
        count += STEP;
        if count > to {
            break;
        }
        sleep_ms(STEP);
    }
}

fn handle_api(ctx: &Context, req: &Request, is_quit: &Arc<AtomicBool>) -> Result<Response> {
    if let Some(api) = api() {
        api.handle_api(ctx, req, is_quit)
    } else {
        Err(Error::UnInit)
    }
}
